package cc.xsubj.trading.world;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;

import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.state.StateBasedGame;
import org.newdawn.slick.tiled.TiledMap;

import cc.xsubj.trading.entity.Interactable;
import cc.xsubj.trading.entity.NPC;
import cc.xsubj.trading.entity.TeleportPad;
import cc.xsubj.trading.entity.Wizard;
import cc.xsubj.trading.messages.Message;
import cc.xsubj.trading.messages.TextOnlyMessage;
import cc.xsubj.trading.utilities.Direction;
import cc.xsubj.trading.utilities.Pair;

import cc.xsubj.engine.Globals;
import cc.xsubj.engine.ResourceManager;
import cc.xsubj.engine.SimpleFollowCamera;
import cc.xsubj.engine.World;
import cc.xsubj.engine.entity.MapEntity;


public class GrassWorld extends World implements TiledWorld {
	
	private TiledMap map;
	
	private boolean[][] blocked;
	
	private Wizard player;
	
	private int tileSize;
	
	private Map<Pair<Integer, Integer>, NPC> npcMap;
	
	private Map<Pair<Integer, Integer>, TeleportPad> teleportPadMap;
	
	private Queue<Message> messageQueue;

	public GrassWorld(int id, GameContainer container, String mapName) {
		super(id, container);
		map = ResourceManager.getMap(mapName);
		this.width = map.getWidth() * map.getTileWidth();
		this.height = map.getHeight() * map.getTileHeight();
	}
	
	@Override
	public void init(GameContainer container, StateBasedGame game)
		throws SlickException {
		super.init(container, game);
		
		MapEntity mapEntity = new MapEntity(map);
		add(mapEntity);
		
		messageQueue = new LinkedList<Message>();
		
		setupKeymaps();
		
		tileSize = map.getTileHeight();
		
		populateBlocked();
		
		npcMap = new HashMap<Pair<Integer, Integer>, NPC>();
		teleportPadMap = new HashMap<Pair<Integer, Integer>, TeleportPad>();
		createMapObjects();
		
		player = new Wizard(64,64);
		add(player);
		
		Rectangle bounds = new Rectangle(0, 0, map.getWidth()*map.getTileWidth(), map.getHeight()*map.getTileHeight());
		setCamera(new SimpleFollowCamera(this, player, (int)(container.getWidth()/Globals.scaleX), (int)(container.getHeight()/Globals.scaleY), bounds));
	}

    private void setupKeymaps()
    {
        define("CONTINUE", Input.KEY_E, Input.KEY_X);
    }

	private void populateBlocked() {
		blocked = new boolean[map.getWidth()][map.getHeight()];
		
		for (int x = 0; x < map.getWidth(); x++)
		{
			for (int y = 0; y < map.getHeight(); y++)
			{
				int tileID = map.getTileId(x, y, 0);
				String value = map.getTileProperty(tileID, "blocked", "false");
				if (value.equals("true"))
				{
					blocked[x][y] = true;
				}
			}
		}
	}
	
	private void createMapObjects()
	{
		for (int groupID = 0; groupID < map.getObjectGroupCount(); groupID++)
		{
			for (int objectID = 0; objectID < map.getObjectCount(groupID); objectID++)
			{
				if (map.getObjectType(groupID, objectID).endsWith("NPC"))
				{
					createNPC(groupID, objectID);
				}
				else if (map.getObjectType(groupID, objectID).equals("TeleportPad"))
				{
				    createTeleportPad(groupID, objectID);
				}
			}
		}
	}
	
	private void createTeleportPad(int groupID, int objectID)
    {
        TeleportPad pad = TeleportPad.createFromTiledMapObject(map, groupID, objectID);
        if (pad != null) {
            this.teleportPadMap.put(new Pair<Integer, Integer>(pad.getMapX(), pad.getMapY()), pad);
        }
    }

    private void createNPC(int groupID, int objectID)
	{
		NPC npc = NPC.createFromTiledMapObject(map, groupID, objectID);
		if (npc != null) {
			this.npcMap.put(new Pair<Integer, Integer>(npc.getMapX(), npc.getMapY()), npc);
			this.add(npc);
		}
		
	}
	
	@Override
	public boolean blocked(int x, int y)
	{
		boolean result = false;
		try {
			result |= blocked[x][y];
		} catch (ArrayIndexOutOfBoundsException e){
		    result = true;
		}

		result |= npcMap.containsKey(new Pair<Integer, Integer>(x, y));
		
		return result;
	}
	
	@Override
	public void update(GameContainer container, StateBasedGame game, int delta)
			throws SlickException
	{
		super.update(container, game, delta);
		handleMessages();
	}
	
	private void handleMessages()
    {
        if(hasMessages())
        {
            Message topMessage = messageQueue.peek();
            if(pressed("CONTINUE"))
            {
                messageQueue.poll();
                topMessage.resolve();
            }
        }
    }

    @Override
	public void render(GameContainer container, StateBasedGame game, Graphics g)
		throws SlickException
	{
		super.render(container, game, g);
		renderTopMessage(container, g);
	}

	private void renderTopMessage(GameContainer container, Graphics g) {
		g.scale(Globals.scaleX, Globals.scaleY);
		if(hasMessages())
		{
		    messageQueue.peek().render(container, g);
		}
	}

	@Override
	public int getTileSize() {
		return tileSize;
	}
	
	@Override
	public Interactable getInteractable(int x, int y)
	{
	    Pair<Integer, Integer> key = new Pair<Integer, Integer>(x,y);
		Interactable result = npcMap.get(key);
		if (result == null)
		    result = teleportPadMap.get(key);
		
		return result;
	}
	
	@Override
	public Direction getPlayerDirection()
	{
	    return player.getDirection();
	}

	@Override
	public void addMessage(Message message)
	{
	    messageQueue.add(message);
	}

    @Override
    public boolean hasMessages()
    {
        return !messageQueue.isEmpty();
    }
    
    public void enter(GameContainer container, StateBasedGame game) throws SlickException
    {
    	super.enter(container, game);
    	System.out.println("Entered state.");
    }
    
}
